<?php

/*

This is open source software.  Please use it freely.
Don't be intimidated by the legalise below; it's just the license
I'm using to provide this as open source software.

Stephen Akins
http://www.google.com/profiles/StephenAkins#contact

Feel free to contact me with questions or corrections/additions/bugs.
If your project helps bring better democracy to more people, then I'll
help you as much as I can for free.



Copyright 2011 Accessvote.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

// lib to write to PDF
require_once(dirname(__FILE__)."/lib/fpdf/fpdf.php");

// lib to import existing PDF documents into FPDF
require_once(dirname(__FILE__)."/lib/fpdi/fpdi.php");

/*

Many thanks to the authors of the fpdf and fpdi libraries (see above).
I have slightly modified the fpdf class and added some functions and
additional features (see below) that I mostly got from fpdf forums and
blogs.

Thanks to you also that I have not named specifically.  You're awesome.

*/


$ballotsource_path = (isset($_GET["s"])) ? (filter_var($_GET["s"], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) : ("");



$page_height = 457.2;
$page_width = 215.9;

$pagenumber_x = $page_width - 31;
$pagenumber_y = 9.5;

$textsize = 11.5;

$column_width = 58.5;
$column_margin = 4;
$row_height = 8.46;


$oddpage_left_margin = 28;
$evenpage_left_margin = 10.6;
$current_left_margin = $oddpage_left_margin;  // the first page is an odd numbered page
$left_padding = 1.8;
$right_padding = 4.6;

$title_top = 24;
$content_top = $title_top + 13.25;
$footer_top = $page_height - 20;


$oval_grid_start_y = 32;
$oval_grid_start_x = 34.8;
$oval_column_width = 9.5;

$option_adjustment_y = 2;

$spacer = 2;

$alert_adjustment = 3.5;


$top_padding = 2;
$bottom_padding = 2;

$heading_height = 7.75;

$image_height = 30.4;
$image_width = 50.8;
$alert_height = 6.36;

$option_border_top = 1;
$option_border_left = .3;

$isfirst_votingevent = true;
$largest_option = 0;

$next_height = 20;
$heading_text = "";



$color_sectionheading_background = array('r' => 0, 'g' => 158, 'b' => 242); // "41B5E8";
$color_subsectionheading_background = array('r' => 212, 'g' => 213, 'b' => 214); // "E2E3E3";
$color_instructions_background = array('r' => 212, 'g' => 242, 'b' => 254); // "E7F6FD";
$color_text = array('r' => 0, 'g' => 0, 'b' => 0); // "000000";
$color_border = array('r' => 0, 'g' => 0, 'b' => 0); // "000000";


$description_text_maxcharsforsinglecolumn = 200;

$current_column = 0;
$current_column_y = 0;
$current_colspan = 1;



defined('TEXT_LINE_HEIGHT') or define('TEXT_LINE_HEIGHT', 5.1);
defined('OPTION_LINE_HEIGHT') or define('OPTION_LINE_HEIGHT', 4.3);
defined('OVAL_WIDTH') or define('OVAL_WIDTH', 3);
defined('OVAL_HEIGHT') or define('OVAL_HEIGHT', 1.25);

class PDF extends FPDI 
{ 
	var $B;
	var $I;
	var $U;
	var $HREF;

	function PDF($orientation='P',$unit='mm',$format='A4') 
	{ 
		//Call parent constructor 
		$this->FPDI($orientation,$unit,$format);
		//Initialization 
		$this->B=0;
		$this->I=0;
		$this->U=0;
		$this->HREF='';
	} 

	function WriteOptionlabel($html) 
	{ 
		//HTML parser 
		$html=str_replace("\n",' ',$html);
		$a=preg_split('/<(.*)>/U',$html,-1,PREG_SPLIT_DELIM_CAPTURE);
		foreach($a as $i=>$e) 
		{ 
			if($i%2==0) 
			{ 
				//Text 
				if($this->HREF) 
				$this->PutLink($this->HREF,$e);
				else 
				$this->Write(OPTION_LINE_HEIGHT,$e);
			} 
			else 
			{ 
				//Tag 
				if($e{0}=='/') 
				$this->CloseTag(strtoupper(substr($e,1)));
				else 
				{ 
					//Extract attributes 
					$a2=explode(' ',$e);
					$tag=strtoupper(array_shift($a2));
					$attr=array();
					foreach($a2 as $v) 
					if(ereg('^([^=]*)=["\']?([^"\']*)["\']?$',$v,$a3)) 
					$attr[strtoupper($a3[1])]=$a3[2];
					$this->OpenTag($tag,$attr,OPTION_LINE_HEIGHT);
				} 
			} 
		} 
	} 

	function WriteDescriptiontext($html) 
	{ 
		//HTML parser 
		$html=str_replace("\n",' ',$html);
		$a=preg_split('/<(.*)>/U',$html,-1,PREG_SPLIT_DELIM_CAPTURE);
		foreach($a as $i=>$e) 
		{ 
			if($i%2==0) 
			{ 
				//Text 
				if($this->HREF) 
				$this->PutLink($this->HREF,$e);
				else 
				$this->Write(TEXT_LINE_HEIGHT,$e);
			} 
			else 
			{ 
				//Tag 
				if($e{0}=='/') 
				$this->CloseTag(strtoupper(substr($e,1)));
				else 
				{ 
					//Extract attributes 
					$a2=explode(' ',$e);
					$tag=strtoupper(array_shift($a2));
					$attr=array();
					foreach($a2 as $v) 
					if(ereg('^([^=]*)=["\']?([^"\']*)["\']?$',$v,$a3)) 
					$attr[strtoupper($a3[1])]=$a3[2];
					$this->OpenTag($tag,$attr,TEXT_LINE_HEIGHT);
				} 
			} 
		} 
	} 

	function OpenTag($tag,$attr,$lineheight) 
	{ 
		//Opening tag 
		if($tag=='B' or $tag=='I' or $tag=='U') 
		$this->SetStyle($tag,true);
		if($tag=='A') 
		$this->HREF=$attr['HREF'];
		if($tag=='BR') 
		$this->Ln($lineheight);
	} 

	function CloseTag($tag) 
	{ 
		//Closing tag 
		if($tag=='B' or $tag=='I' or $tag=='U') 
		$this->SetStyle($tag,false);
		if($tag=='A') 
		$this->HREF='';
	} 

	function SetStyle($tag,$enable) 
	{ 
		//Modify style and select corresponding font 
		$this->$tag+=($enable ? 1 : -1);
		$style='';
		foreach(array('B','I','U') as $s) 
		if($this->$s>0) 
		$style.=$s;
		$this->SetFont('',$style);
	} 

	function PutLink($URL,$txt) 
	{ 
		//Put a hyperlink 
		$this->SetTextColor(0,0,255);
		$this->SetStyle('U',true);
		$this->Write(OPTION_LINE_HEIGHT,$txt,$URL);
		$this->SetStyle('U',false);
		$this->SetTextColor(0);
	}

	function NbLines($w, $txt) // not calculating HTML bold/regular properly (ignores bold tags)
	{
		//Computes the number of lines a MultiCell of width w will take
		$cw=&$this->CurrentFont['cw'];
		if($w==0)
			$w=$this->w-$this->rMargin-$this->x;
		$wmax=($w-2*$this->cMargin)*1000/$this->FontSize;
		$s=str_replace("\r", '', $txt);
		$s=str_replace("<BR>", "\n", $s); // use double quotes for special character
		$s=str_replace("<B>", '', $s);
		$s=str_replace("</B>", '', $s);
		$nb=strlen($s);
		if($nb>0 and $s[$nb-1]=="\n")
			$nb--;
		$sep=-1;
		$i=0;
		$j=0;
		$l=0;
		$nl=1;
		while($i<$nb)
		{
			$c=$s[$i];
			if($c=="\n")
			{
				$i++;
				$sep=-1;
				$j=$i;
				$l=0;
				$nl++;
				continue;
			}
			if($c==' ')
				$sep=$i;
			$l+=$cw[$c];
			if($l>$wmax)
			{
				if($sep==-1)
				{
					if($i==$j)
						$i++;
				}
				else
					$i=$sep+1;
				$sep=-1;
				$j=$i;
				$l=0;
				$nl++;
			}
			else
				$i++;
		}
		
		return $nl;
	}

	function Circle($x, $y, $r, $style='D')
	{
		$this->Ellipse($x,$y,$r,$r,$style);
	}
	
	function Ellipse($x, $y, $rx, $ry, $style='D')
	{
		if($style=='F')
			$op='f';
		elseif($style=='FD' || $style=='DF')
			$op='B';
		else
			$op='S';
		$lx=4/3*(M_SQRT2-1)*$rx;
		$ly=4/3*(M_SQRT2-1)*$ry;
		$k=$this->k;
		$h=$this->h;
		$this->_out(sprintf('%.2F %.2F m %.2F %.2F %.2F %.2F %.2F %.2F c',
			($x+$rx)*$k,($h-$y)*$k,
			($x+$rx)*$k,($h-($y-$ly))*$k,
			($x+$lx)*$k,($h-($y-$ry))*$k,
			$x*$k,($h-($y-$ry))*$k));
		$this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c',
			($x-$lx)*$k,($h-($y-$ry))*$k,
			($x-$rx)*$k,($h-($y-$ly))*$k,
			($x-$rx)*$k,($h-$y)*$k));
		$this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c',
			($x-$rx)*$k,($h-($y+$ly))*$k,
			($x-$lx)*$k,($h-($y+$ry))*$k,
			$x*$k,($h-($y+$ry))*$k));
		$this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c %s',
			($x+$lx)*$k,($h-($y+$ry))*$k,
			($x+$rx)*$k,($h-($y+$ly))*$k,
			($x+$rx)*$k,($h-$y)*$k,
			$op));
	}

}





function writeHeading() {

	global $pdf, $textsize, $current_left_margin, $oddpage_left_margin, $right_padding, $current_page, $current_column, $column_width, $page_width, $pagenumber_x, $pagenumber_y;
	
	global $heading_text;

	// write the page heading
	$pdf->SetFont('Helvetica','', $textsize * .85);
	
	$pdf->SetLeftMargin($oddpage_left_margin-1); // The margin is the same on the even pages as it is on the odd pages
	$pdf->SetRightMargin($page_width - ($oddpage_left_margin + (2 * $column_width)) + $right_padding);
	$pdf->SetXY($oddpage_left_margin-1, 3.25); 
	
	$pdf->WriteOptionlabel($heading_text);

	// write the page number
	$pdf->SetFont('Helvetica','B', $textsize * 3);

	if($current_page > 9) $pagenumber_x -= 7;

	$pdf->SetLeftMargin($current_left_margin + (2 * $column_width)-1);
	$pdf->SetRightMargin($right_padding);
	$pdf->SetXY($pagenumber_x, $pagenumber_y);

	$pdf->WriteOptionlabel("$current_page");

	$pdf->SetXY($pdf->GetX(), $pagenumber_y + 2.25);
	$pdf->SetFont('Helvetica','B', $textsize);
	$pdf->WriteOptionlabel(" / {t}");

}




function setColumn($column) {

	global $pdf, $template_oddpages, $template_evenpages, $template_path;
	global $current_column, $current_column_y, $current_width, $current_left_margin, $current_page, $current_colspan;
	global $oddpage_left_margin, $evenpage_left_margin, $left_padding, $right_padding, $column_width, $title_top, $page_width;
	global $spacer;

	// is the next column on the next page?
	if($column > 2) {

		// first add the "next side" message
		$pdf->SetLeftMargin($current_left_margin + $left_padding + (($current_column+$current_colspan-1) * $column_width));
		$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + $current_colspan) * $column_width)) + $right_padding);
		$pdf->Image("../../images/pdf/arrow_full.png", ($current_left_margin + (($current_column+$current_colspan-1) * $column_width)), $current_column_y + 3, ($column_width - $left_padding - $column_margin), 148/(656 / ($column_width - $column_margin)));
		$pdf->SetXY(($current_left_margin + (($current_column+$current_colspan-1) * $column_width) + $left_padding), $current_column_y + 5);
		if ($current_page % 2) {
			$pdf->WriteOptionlabel("Continue voting<BR>next side");
		} else {
			$pdf->WriteOptionlabel("Continue voting<BR>next page");
		}

		// add the page and use the template
		$pdf->AddPage();
		$current_page++;
		if ($current_page % 2) {
			if($template_path != "") $pdf->useTemplate($template_oddpages);
			$current_left_margin = $oddpage_left_margin;
		} else {
			if($template_path != "") $pdf->useTemplate($template_evenpages);
			$current_left_margin = $evenpage_left_margin;
		}
		$column -= 3;
//		$current_colspan = 1;

		writeHeading();

	}

	$current_column_y = $title_top;
	$current_column = $column;

	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($column * $column_width));
	$pdf->SetRightMargin($page_width - ($current_left_margin + ($column * $column_width) + $column_width) + $right_padding);

	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);

}




function getTextHeight($text, $left_padding = 0, $right_padding = 0) {

	global $pdf, $column_width, $column_margin, $row_height;
	global $current_colspan;
	
	
	$text_height = ($text == "") ? 0 : $pdf->NbLines(($column_width * ($current_colspan)) - ($left_padding + $right_padding), $text) * TEXT_LINE_HEIGHT;

	return $text_height;

}





function getOptionHeight($option_text, $left_padding = 0, $right_padding = 0) {
	
	global $pdf, $column_width, $column_margin, $row_height;
	global $oval_column_width;

	$option_height = 0;
	

	// Find out the number of lines, add one for spacing (if there is more than one line), and then multiply by the line height
	$option_text_height = ($option_text == "") ? 0 : ($pdf->NbLines($column_width - ($left_padding + $right_padding + $oval_column_width), $option_text));
	$option_text_height += ($option_text_height > 1) ? 1 : 0;
	$option_text_height *= OPTION_LINE_HEIGHT;

	// find out how many grid lines it would take to accomodate the new data
	while($option_text_height > $row_height) {
		$option_text_height -= $row_height;
		$option_height += $row_height;
	}
	$option_height += $row_height;
	
	return $option_height;

}





function getNextOvalY($my_current_column_y) {

	global $oval_grid_start_y, $row_height, $option_adjustment_y;
	
	for($i=0 ; $i<48 ; $i++)
		if($oval_grid_start_y + ($i * $row_height) - $option_adjustment_y > $my_current_column_y) return $oval_grid_start_y + ($i * $row_height) - $option_adjustment_y;

	return 0;

}




function drawDashedLine() {

	global $pdf;
	global $current_column_y, $current_left_margin, $current_column, $current_colspan;
	global $column_width, $oval_column_width;
	global $left_margin, $right_margin;

	$dashedline_height = .2;
	$dashedline_width = .9;

	for($i=1 ; $i<=30*$current_colspan ; $i++) {

		$pdf->SetFillColor($color_border['r'], $color_border['g'], $color_border['b']);
		$pdf->Rect(
				$current_left_margin + $left_padding + ($current_column * $column_width) + $oval_column_width + 1.5 + ($i * 1.4),
				$current_column_y,
				$dashedline_width,
				$dashedline_height,
				"F");

	}

}




function drawSolidLine() {

	global $pdf;
	global $current_column_y, $current_left_margin, $current_column, $current_colspan;
	global $column_width, $oval_column_width;
	global $left_margin, $right_margin;

	$solidline_height = .2;

	$pdf->SetFillColor($color_border['r'], $color_border['g'], $color_border['b']);
	$pdf->Rect(
			$current_left_margin + $left_padding + ($current_column * $column_width) + $oval_column_width + 1.5 + 1.4,
			$current_column_y,
			($column_width * $current_colspan) - $left_margin - $right_margin - $oval_column_width - 8,
			$solidline_height,
			"F");

}



function calculateVotingeventHeight($options, &$heading_text, $instructions_text, $description_text, $write_ins, $colspan = 1) {

	global $pdf;
	global $option_border_top, $spacer;
	global $current_left_margin, $current_column, $current_column_y;
	global $left_padding, $right_padding, $column_width;

	// TODO: I don't think the following three lines are needed
	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width));
	$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + $colspan) * $column_width)) + $right_padding);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);

	$pdf->SetFont('Helvetica','B', $textsize);
	$heading_height = getTextHeight($heading_text, $left_padding, $right_padding);

	if($heading_height > TEXT_LINE_HEIGHT) {
		$heading_text = str_replace(": \n", ": ", $heading_text);
		$heading_text = str_replace(": ", ": \n", $heading_text); // if the heading is more than one line, add newline so it wraps after a colon.
		$previous_heading_height = $heading_height;
		$heading_height = getTextHeight($heading_text, $left_padding, $right_padding);
		if($previous_heading_height < $heading_height) {
			$heading_text = str_replace(": \n", ": ", $heading_text);
		}
	}


	$pdf->SetFont('Helvetica','B', $textsize);
	$instructions_height = getTextHeight($instructions_text, $left_padding + (($is_alert) ? ($alert_adjustment) : (0)), $right_padding);


	$pdf->SetFont('Helvetica','', $textsize);
	$description_height = ($description_text == "") ? 0 : getTextHeight($description_text, $left_padding, $right_padding);

	$votingevent_options_height = calculateVotingeventOptionsHeight($options, $write_ins, $colspan);



	// we have all the values, now put them together to calculate the height

	$projected_votingevent_height = 0;
	$projected_votingevent_height += $option_border_top;
	$projected_votingevent_height += $heading_height + $spacer*2;
	$projected_votingevent_height += $instructions_height + $spacer*2;
	if($description_text != "") {

		$projected_votingevent_height += $description_height + $spacer*2;

	}
	$distance_to_next_oval = getNextOvalY($current_column_y + $projected_votingevent_height) - ($current_column_y + $projected_votingevent_height);
	$projected_votingevent_height += ($distance_to_next_oval > 0) ? $distance_to_next_oval : 0;
	$projected_votingevent_height += $votingevent_options_height + $spacer;
	$projected_votingevent_height += $option_border_top;


	return $projected_votingevent_height;

}




function calculateVotingeventOptionsHeight(&$options, $write_ins, $colspan = 1) { // options are passed by reference in case content formatting needs to be adjusted

	global $pdf, $column_width, $column_margin, $row_height, $textsize;
	global $color_sectionheading_background, $color_subsectionheading_background, $color_instructions_background;
	global $page_height, $page_width;
	global $current_column, $current_column_y, $oval_column_width;
	global $current_left_margin, $left_padding, $right_padding;
	global $title_top, $content_top, $footer_top, $spacer;

	global $top_padding, $bottom_padding;
	global $heading_height;
	global $image_height, $image_width, $alert_height;

	global $largest_option;


	// We figure out how much of the column is going to be needed for the options.  (and see if we can fit the votingevent in the current column)

	// First we set the correct margins
	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width) + $oval_column_width);
	$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + $current_colspan) * $column_width)) + $right_padding);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + $oval_column_width, $current_column_y);


	// We'll set the style to Bold so that the text wrapping is calculated correctly
	$pdf->SetFont('Helvetica','B', $textsize);

//	$votingevent_options_height = getNextOvalY($current_column_y) - $current_column_y;
//	$votingevent_options_height = ($votingevent_options_height < 0) ? (0) : ($votingevent_options_height);
	$votingevent_options_height=0;
	$largest_option = 0;

	for($i=0 ; $i<sizeof($options) ; $i++) {

		$current_option_text = "";

		// if there is a optiongroup (party) then bold the options

//		if(sizeof($options[$i]['optiongroup']) > 0) 
//			$current_option_text .= "<B>";
		$current_option_text .= "<B>";

		$largest_option = ($largest_option >= sizeof($options[$i]["option"])) ? ($largest_option) : sizeof($options[$i]["option"]);

		// add the option/candidate names
		for($j=0 ; $j<sizeof($options[$i]["option"])-1 ; $j++) {

			if($pdf->NbLines($column_width - ($left_padding + $right_padding + $oval_column_width), $options[$i]["option"][$j]) > 1) 
				$options[$i]["option"][$j] = str_replace("-", "- ", $options[$i]["option"][$j]); // if the name is more than one line, add spaces after the hyphens (if any) to make sure the name wraps correctly.

			$current_option_text .= $options[$i]["option"][$j] . "<BR>and<BR>";
		
		}
		
		if($pdf->NbLines($column_width - ($left_padding + $right_padding + $oval_column_width), $options[$i]["option"][$j]) > 1) 
			$options[$i]["option"][$j] = str_replace("-", "- ", $options[$i]["option"][$j]); // we have this check a second time because the last option isn't in it.
		
		$current_option_text .= $options[$i]["option"][$j];

		
//		if(sizeof($options[$i]['optiongroup']) > 0) $current_option_text .= "</B>";
		$current_option_text .= "</B>";

		// add the party (if there is one)
		if(sizeof($options[$i]['optiongroup']) > 0)
			$current_option_text .= "<BR>" . $options[$i]['optiongroup'];



		

		$current_option_height = getOptionHeight($current_option_text, $left_padding, $right_padding);

		$rows_required = 1;
		while($current_option_height > $row_height) {

			$rows_required++;
			$current_option_height-=$row_height;

		}

		$votingevent_options_height += $rows_required * $row_height;

	}



	if($write_ins) {

		$votingevent_options_height += OPTION_LINE_HEIGHT;
		for($i=1 ; $i<=$write_ins ; $i++) {

			for($j=1 ; $j<=$largest_option ; $j++) {
	
				$votingevent_options_height += $row_height;

			}


			if($i < $write_ins) {
				$votingevent_options_height += $row_height;

			}

		}

	}

//	$votingevent_options_height += ($largest_option == 1) ? ($row_height + OPTION_LINE_HEIGHT + $spacer) : $spacer;
	$votingevent_options_height += $spacer;


	return $votingevent_options_height;

}




function add_instructions($heading_text, $instructions_text, $illustration_filename, $alert_text = "") {

	global $pdf, $column_width, $column_margin, $row_height, $textsize;
	global $color_sectionheading_background, $color_subsectionheading_background, $color_instructions_background;
	global $page_height, $page_width;
	global $current_column, $current_column_y;
	global $current_left_margin, $left_padding, $right_padding;
	global $title_top, $content_top, $footer_top, $spacer;

	global $top_padding, $bottom_padding;
	global $heading_height;
	global $image_height, $image_width, $alert_height;
	
	$is_alert = ($alert_text == "") ? false : true;



	$pdf->SetFont('Helvetica','', $textsize);
	$instructions_height = getTextHeight($instructions_text, $left_padding, $right_padding);


	$pdf->SetFont('Helvetica','B', $textsize);
	$alert_text_height = getTextHeight($alert_text, $left_padding, $right_padding);
	
	$instructionbox_height = ($is_alert) ? ($top_padding + $heading_height + $image_height + $spacer + $instructions_height + $spacer + $alert_height + $spacer + $alert_text_height + $bottom_padding) : ($top_padding + $heading_height + $image_height + $spacer + $instructions_height + $bottom_padding);




	if($instructionbox_height + $current_column_y > $footer_top) {
		setColumn($current_column+1);
	}


	$pdf->SetFillColor($color_instructions_background['r'], $color_instructions_background['g'], $color_instructions_background['b']);

	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$current_column_y,
			($column_width + $left_padding - $column_margin),
			$instructionbox_height,
			"F");

	
	
	$current_column_y += 1.75;
		
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
	$pdf->SetTextColor($color_text['r'], $color_text['g'], $color_text['b']);
	$pdf->Write(TEXT_LINE_HEIGHT, $heading_text);
	
	$current_column_y += 6.5;
	
	$pdf->Image("../../images/pdf/" . $illustration_filename, ($current_left_margin + ($current_column * $column_width) + $left_padding + 1), $current_column_y, $image_width, $image_height);

	$current_column_y += $image_height + $spacer;

	$pdf->SetFont('Helvetica','', $textsize);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
	$pdf->Write(TEXT_LINE_HEIGHT, $instructions_text);

	$current_column_y += $instructions_height;

	if($is_alert) {

		$current_column_y += $spacer;

		$pdf->Image("../../images/pdf/alert.png", ($current_left_margin + ($current_column * $column_width) + $left_padding + 1), $current_column_y, $alert_height, $alert_height);
		
		$current_column_y += $alert_height + $spacer;
		
		$pdf->SetFont('Helvetica','B', $textsize);
		$pdf->SetTextColor($color_sectionheading_background['r'], $color_sectionheading_background['g'], $color_sectionheading_background['b']);
		$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
		$pdf->Write(TEXT_LINE_HEIGHT, $alert_text);

		$current_column_y += $alert_text_height;
	
	}

	$current_column_y += $bottom_padding + $spacer;

}





function add_votingevent($heading_text, $instructions_text, $options, $description_text = "", $is_alert = false, $write_ins = 0) {


	global $pdf, $column_width, $column_margin, $oval_column_width, $row_height;
	global $color_sectionheading_background, $color_subsectionheading_background, $color_instructions_background;
	global $page_height, $page_width;
	global $current_column, $current_column_y, $current_colspan;
	global $current_left_margin, $left_padding, $right_padding;
	global $title_top, $content_top, $footer_top, $spacer, $textsize, $alert_adjustment;
	global $alert_height, $description_text_maxcharsforsinglecolumn;

	global $top_padding, $bottom_padding;
	global $heading_height;
	global $image_height, $image_width, $alert_height, $next_height;
	global $option_border_top, $option_border_left;

	global $isfirst_votingevent;
	global $largest_option;
	
	
	$thankyou_height = 40;

	// To draw the borders/background, we first have to figure out how large the votingevent is going to be, and where there is enough room to put it.  
	// To calculate the height of the votingevent, we need to calculate five things:
	// heading height, instructions height, description height, question/options height (content), and the height of the margins/borders/spaces.  Also, the questions have to fit on the oval grid so none content area items are made to fit the space between content areas

	// if the votingevent has a large description (which usually indicates a referendum question), then the votingevent should take up more than one column.  The number of columns depends first on which column is the current one.  If the next available column is the second one, then first we check if the large question can fit in the last two columns.  If two columns isn't enough, then we start on the next page/side with 3 columns.



	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width));
	$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + $current_colspan) * $column_width)) + $right_padding);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);

	$pdf->SetFont('Helvetica','B', $textsize);
	$heading_height = getTextHeight($heading_text, $left_padding, $right_padding);

	$pdf->SetFont('Helvetica','B', $textsize);
	$instructions_height = getTextHeight($instructions_text, $left_padding + (($is_alert) ? ($alert_adjustment) : (0)), $right_padding);


	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width));
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);

	$pdf->SetFont('Helvetica','', $textsize);
	$description_height = getTextHeight($description_text, $left_padding, $right_padding);




	if(strlen($description_text) > $description_text_maxcharsforsinglecolumn ||
		(strlen($description_text) != 0 && $current_colspan != 1)) {

		// This is a referendum style question or a race with a large description text.
		// To accomodate the votingevent we will span one or two columns depending on what is appropriate.

		if($current_column == 0) { // the first column is actually the zeroth column
			// We're in the first column; we automatically start at the top of column two (unless it's the very first item on the whole ballot - in which case we stay where we are) and span the last two columns
			if($current_colspan == 1) {
				setColumn(1);
				$current_colspan = 2;
			}
			$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, $current_colspan);


			// check to see if it fits, if not, start a new column
			if($votingevent_height + $current_column_y > $footer_top - $next_height) {
	
				setColumn(3);
				$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
				$current_colspan = 3;
				$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, $current_colspan);
		
			}



		} else if($current_column == 1) { // second column
			// We're in the second column; we see if there's enough room, if so we span two columns.  if not we start a new page and span all three columns
			$current_colspan = 2;
			$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, 2);
			if($votingevent_height + $current_column_y > $footer_top - $thankyou_height) {
				setColumn(3); // column 3 is the first column on the next page
				$current_colspan = 3;
				$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, 3);
			}
		} else if($current_column == 2) { // third column
			// We're in the third column, so we'll start on a new page and span all three columns
			setColumn(3); // column 3 is the first column on the next page
			$current_colspan = 3;
			$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, 3);
		}

	} else {

		if($current_colspan != 1) {
			// if the votingevent is using a single column after having previous spanned multiple columns, then we start a new page 
			setColumn(3); 
			$current_colspan = 1;
		}
		$votingevent_height = calculateVotingeventHeight($options, $heading_text, $instructions_text, $description_text, $write_ins, 1);

		// check to see if it fits, if not, start a new column
		if($votingevent_height + $current_column_y > $footer_top ||
			($current_column == 2 && $votingevent_height + $current_column_y > $footer_top - $next_height)) {

			setColumn($current_column+1);
			$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
	
		}

	}

	$pdf->SetFont('Helvetica','', $textsize);
	$description_height = getTextHeight($description_text, $left_padding, $right_padding);




	// We have a space that can accomodate the votingevent.  Now lets start drawing them
	$votingevent_options_height = calculateVotingeventOptionsHeight($options, $write_ins, $current_colspan);



	// set the margins

	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width));
	$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + $current_colspan) * $column_width)) + $right_padding);

	$pdf->SetTextColor($color_text['r'], $color_text['g'], $color_text['b']);
	$pdf->SetFont('Helvetica','B', $textsize);

	$votingevent_top = $current_column_y;



	// Draw the top borders and headings

	$pdf->SetFillColor($color_border['r'], $color_border['g'], $color_border['b']);
	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$votingevent_top,
			(($column_width * $current_colspan) + $left_padding - $column_margin),
			$option_border_top,
			"F");
	$current_column_y += $option_border_top;

	$pdf->SetFillColor($color_subsectionheading_background['r'], $color_subsectionheading_background['g'], $color_subsectionheading_background['b']);
	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$current_column_y,
			(($column_width * $current_colspan) + $left_padding - $column_margin),
			$heading_height + $spacer*2,
			"F");
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y + 1.75);
	$pdf->Write(TEXT_LINE_HEIGHT, $heading_text);
	$current_column_y += $heading_height + $spacer*2;

	$pdf->SetFillColor($color_instructions_background['r'], $color_instructions_background['g'], $color_instructions_background['b']);
	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$current_column_y,
			(($column_width * $current_colspan) + $left_padding - $column_margin),
			$instructions_height + $spacer*2,
			"F");
	if($is_alert) $pdf->Image("../../images/pdf/alert.png", ($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y+2.5, $alert_height * .6, $alert_height * .6);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + (($is_alert) ? ($alert_adjustment) : (0)), $current_column_y + 1.75);
	$pdf->Write(TEXT_LINE_HEIGHT, $instructions_text);
	$current_column_y += $instructions_height + $spacer*2;


	$pdf->SetFont('Helvetica','', $textsize);

	if($description_text != "") {

		$current_column_y += $spacer;
		$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
		$pdf->WriteDescriptiontext($description_text);
		$current_column_y += $description_height + $spacer;

	}



	// then we draw the options

	// the options are indented the width of the oval column
	$pdf->SetLeftMargin($current_left_margin + $left_padding + ($current_column * $column_width) + $oval_column_width);
	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + $oval_column_width, $current_column_y);

	// find the starting grid row
	$temp_column_y = $current_column_y;
	$current_column_y = getNextOvalY($current_column_y+1);
	$votingevent_height += $current_column_y - $temp_column_y; // adjust the height to allow for the grid correction
	$votingevent_options_height += $current_column_y - $temp_column_y; // adjust the height to allow for the grid correction
	$largest_option = 0;

	for($i=0 ; $i<sizeof($options) ; $i++) {

		// draw the oval for the current option
		$pdf->Ellipse(($current_left_margin + $left_padding + (OVAL_WIDTH*1.7) + ($current_column * $column_width)), $current_column_y + (OVAL_HEIGHT*1.7), OVAL_WIDTH, OVAL_HEIGHT, 'D');

		$current_option_text = "";

		$current_option_text .= "<B>";

		$largest_option = ($largest_option >= sizeof($options[$i]["option"])) ? ($largest_option) : sizeof($options[$i]["option"]);

		// add the option/candidate names
		for($j=0 ; $j<sizeof($options[$i]["option"])-1 ; $j++) {

			$current_option_text .= $options[$i]["option"][$j] . "<BR>and<BR>";

		}
		$current_option_text .= $options[$i]["option"][$j];

		$current_option_text .= "</B>";

		// add the party (if there is one)
		if(sizeof($options[$i]['optiongroup']) > 0)
			$current_option_text .= "<BR>" . $options[$i]['optiongroup'];



		
		// Now we have the text, we calculate how tall it is	
		$current_option_height = getOptionHeight($current_option_text, $left_padding, $right_padding);



		// Write the option out
		$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + $oval_column_width, $current_column_y);
		$pdf->WriteOptionlabel($current_option_text);
		$current_column_y += $current_option_height;

		$current_column_y -= 2.5;
		drawSolidLine();
		if($i < sizeof($options)-1 || $write_ins) $current_column_y += 2.5;

	}

	// if the votingevent allows writeins, loop through them and add them below.	
	if($write_ins) {

		for($i=1 ; $i<=$write_ins ; $i++) {

			$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + $oval_column_width, $current_column_y);

			$pdf->Ellipse(($current_left_margin + $left_padding + (OVAL_WIDTH*1.7) + ($current_column * $column_width)), $current_column_y + (OVAL_HEIGHT*1.7), OVAL_WIDTH, OVAL_HEIGHT, 'D');
	
			$pdf->SetFont('Helvetica','B', $textsize);
			$pdf->Write(OPTION_LINE_HEIGHT, "or write-in:");
			$current_column_y += OPTION_LINE_HEIGHT;
	
			// draw a line for each row in the largest option
			for($j=1 ; $j<=$largest_option ; $j++) {
	
				$current_column_y += $row_height;
				drawDashedLine();
	
			}

			if($i < $write_ins) $current_column_y = getNextOvalY($current_column_y);

		}

	}



	$description_offset = ($description_text = "") ? (0) : $description_height + $spacer;
	// draw the left and bottom borders for the votingevent
	$pdf->SetFillColor($color_border['r'], $color_border['g'], $color_border['b']);
	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$votingevent_top,
			$option_border_left,
			$heading_height + $spacer*2 + $option_border_top +
				$instructions_height + $spacer*2 +
				$description_offset +
				$votingevent_options_height + $option_border_left,
			"F");
	$current_column_y += $option_border_top; // getting rid of this... see below


	$current_column_y = $votingevent_top +
				$option_border_top +
				$heading_height + $spacer*2 + 
				$instructions_height + $spacer*2 +
				$description_offset + 
				$votingevent_options_height;


	// Now draw the bottom border
	$pdf->SetFillColor($color_border['r'], $color_border['g'], $color_border['b']);
	$pdf->Rect(
			$current_left_margin + ($current_column * $column_width),
			$current_column_y,
			(($column_width * $current_colspan) + $left_padding - $column_margin),
			$option_border_left,
			"F");

	$isfirst_votingevent = false;

	$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding) + $oval_column_width, $current_column_y);

}



$ballotXML = $_POST['s'];

// =======================================================================================
// Read The XML file
if($ballotsource_path == "" && $ballotXML == "") {

	$pageURL = 'http';
	if ($_SERVER["HTTPS"] == "on") $pageURL .= "s";
	$pageURL .= "://";
	if ($_SERVER["SERVER_PORT"] != "80") {
		$pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
	} else {
		$pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
	}


    echo "Error: You must include a XML ballot source; either by passing the URL of a file that contains the source data (using the parameter 's'), or by posting the data (again, using the parameter 's').<br>\n(example $pageURL?s=http://accessvote.com/sample_ballots/us_eac-sample.xml)";
    exit;
}


if($ballotsource_path != "") {

	if (!@fopen($ballotsource_path,"r")) {
	    echo "The XML source file ($ballotsource_path) does not exist";
	    exit;
	}
	$ballot_hash = hash_file ('sha512', $ballotsource_path);

} else {

	$ballot_hash = hash ('sha512', $ballotXML);

}

$ballotsource_localfilename = "ballots/" . $ballot_hash . ".xml";

$ballot_title = "";
$ballot_location = "";
$ballot_date = "";



$xmlDoc = new DOMDocument();
if($ballotsource_path != "") {
	$xmlDoc->load($ballotsource_path);
} else {
	$xmlDoc->loadXML($ballotXML);
}

$x = $xmlDoc->documentElement;
foreach ($x->childNodes AS $item)
{
	if(strtolower($item->nodeName) == "template") {
		$template_path = $item->nodeValue;

		foreach ($item->childNodes as $subitem) {

			if(strtolower($subitem->nodeName) == "path") {

				$template_path = $subitem->nodeValue;

			} else if(strtolower($subitem->nodeName) == "style") {

				$template_style = $subitem->nodeValue;

			}

		}

	} else if(strtolower($item->nodeName) == "header") {

		foreach ($item->childNodes as $subitem) {

			if(strtolower($subitem->nodeName) == "title") {

				$ballot_title = $subitem->nodeValue;

			} else if(strtolower($subitem->nodeName) == "location") {

				$ballot_location = $subitem->nodeValue;

			} else if(strtolower($subitem->nodeName) == "date") {

				$ballot_date = $subitem->nodeValue;

			}

		}

	}
}

$data = ($ballotsource_path == "") ? ($ballotXML) : (file_get_contents($ballotsource_path));
$fp = fopen($ballotsource_localfilename, "wb");
fwrite($fp, $data);
fclose($fp);


if($template_path != "") {

	if (!@fopen($template_path,"r")) {
	    echo "The PDF template file ($template_path) does not exist";
	    exit;
	}

	$template_hash = hash_file ('sha512', $template_path);
	$template_localfilename = "ballots/" . $ballot_hash . ".pdf";


	$data = file_get_contents($template_path);
	$fp = fopen($template_localfilename, "wb");
	fwrite($fp, $data);
	fclose($fp);

}

// check to see if there are any writeins so we know whether or not we need to display the instructions for writeins
$are_writeins = false;

foreach ($x->childNodes AS $item)
{
	if($item->nodeName != "#text") {
		if(strtolower($item->nodeName) == "votingevent") {
			foreach ($item->childNodes AS $subitem) {
				if($subitem->nodeName != "#text") {
					if(strtolower($subitem->nodeName) == "writeins") {
						if($subitem->nodeValue > 0) $are_writeins = true;
					}
				}
			}
		}
	}
}





// =======================================================================================
// Start Rendering


$pdf = new PDF('P', 'mm', array($page_width, $page_height));

$pdf->AddPage();
$current_page = 1;

// import the template PFD
if($template_path != "") {

	$pdf->setSourceFile($template_localfilename);

	// select the first page
	$template_oddpages = $pdf->importPage(1);
	$template_evenpages = $pdf->importPage(2);

	// use the page we imported
	$pdf->useTemplate($template_oddpages);

}

$heading_text = "<B>$ballot_title<BR>$ballot_location<BR>$ballot_date</B>";

writeHeading();


// add QR Code receipt
// $pdf->Image('https://chart.googleapis.com/chart?chs=500x500&cht=qr&chld=M&chl=http://accessvote.com/b/?1234567890ABCDEFGHIJ1234567890ABCDEFGHIJ&choe=UTF-8&.png', 145, 100, 18, 18);

// The above is for an upcoming feature that will give users the option of adding a QR code that will allow the user to get a "receipt" that will tell them if their vote has been counted properly.


setColumn($current_column);

$pdf->SetFillColor($color_sectionheading_background['r'], $color_sectionheading_background['g'], $color_sectionheading_background['b']);
$pdf->Rect($current_left_margin + ($current_column * $column_width), $current_column_y, ($column_width + $left_padding - $column_margin), 12, "F");


$current_column_y += 6; // the rectangle is 12mm tall, and the title is in the middle at 6mm

$pdf->SetFont('Helvetica','B', $textsize);
$pdf->SetTextColor(255,255,255);

$pdf->SetXY(($current_left_margin + ($current_column * $column_width) + $left_padding), $current_column_y);
$pdf->Write(0, "Instructions");

$current_column_y += 7.25;


add_instructions("Making selections", "Fill in the oval to the left of the name of your choice. You must blacken the oval completely, and do not make any marks outside of the oval. You do not have to vote in every race. ", "instructions_1.png", "Do not cross out or erase, or your vote may not count. If you make a mistake or a stray mark, ask for a new ballot from the poll workers.");


if($are_writeins) add_instructions("Optional write-in", "To add a candidate, fill in the oval to the left of \"or write-in\" and print the name clearly on the dotted line.", "instructions_2.png", "");


add_instructions("Turning in the ballot", "Insert the completed ballot into the ballot sleeve. Hand in the ballot to be counted.", "instructions_3.png", "Do not fold the ballot.");




function decode($original_string) {

	if($original_string == "") return "";

	$string_parts = split("\*", $original_string);
	$current_state = "closed";
	$processed_string = "";

	// create BOLD tags
	for($i = 0 ; $i < sizeof($string_parts) ; $i++) {

		$processed_string .= $string_parts[$i];
		if($i < sizeof($string_parts)-1) {
			if($current_state == "closed") {
	
				$current_state = "opened";
				$processed_string .= "<B>";
				
	
			} else {
	
				$current_state = "closed";
				$processed_string .= "</B>";
	
			}
		}

	}

	$string_parts = split("\_", $processed_string);
	$current_state = "closed";
	$processed_string = "";

	// create BOLD tags
	for($i = 0 ; $i < sizeof($string_parts) ; $i++) {

		$processed_string .= $string_parts[$i];
		if($i < sizeof($string_parts)-1) {
			if($current_state == "closed") {
	
				$current_state = "opened";
				$processed_string .= "<I>";
	
			} else {
	
				$current_state = "closed";
				$processed_string .= "</I>";
	
			}
		}

	}

	$processed_string = preg_replace("/\n[ \t]*/i", "<BR>", $processed_string);

	return $processed_string;

}



foreach ($x->childNodes AS $item)
{
	if($item->nodeName != "#text") {
		if(strtolower($item->nodeName) == "votingevent") {

			$votingevent_options = array();
			$votingevent_heading = "";
			$votingevent_instructions = "";
			$votingevent_description = "";
			$votingevent_alert = false;
			$write_ins = 0;

			foreach ($item->childNodes AS $subitem) {
				if($subitem->nodeName != "#text") {
					if(strtolower($subitem->nodeName) == "option") {
						$option_namelist = array();
						$option_group = "";
						foreach ($subitem->childNodes AS $subsubitem) {
							if(strtolower($subsubitem->nodeName) != "#text") {
								if(strtolower($subsubitem->nodeName) == "name") {
									array_push($option_namelist, trim($subsubitem->nodeValue));
								}
								else if(strtolower($subsubitem->nodeName) == "party")
									$option_group = trim($subsubitem->nodeValue);
							}
						}
						array_push($votingevent_options, array('option' => $option_namelist, 'optiongroup' => $option_group));
					} else if(strtolower($subitem->nodeName) == "heading") {
						$votingevent_heading = trim($subitem->nodeValue);
					} else if(strtolower($subitem->nodeName) == "instructions") {
						$votingevent_instructions = trim($subitem->nodeValue);
					} else if(strtolower($subitem->nodeName) == "description") {
						$votingevent_description = decode(trim($subitem->nodeValue));
					} else if(strtolower($subitem->nodeName) == "alert") {
						$votingevent_alert = (strtolower(trim($subitem->nodeValue)) == "true") ? (true) : (false);
					} else if(strtolower($subitem->nodeName) == "writeins") {
						$write_ins = trim($subitem->nodeValue);
					}
				}
			}

			add_votingevent(
				$votingevent_heading, // heading
				$votingevent_instructions, // instructions text
				$votingevent_options,
				$votingevent_description, // description
				$votingevent_alert, // alert
				$write_ins // write_in
			);
		}
	}
}


// write the "Thank you for voting" message

$current_column_y += $spacer;

$pdf->SetFillColor($color_instructions_background['r'], $color_instructions_background['g'], $color_instructions_background['b']);

$pdf->Rect(
		$current_left_margin + (($current_column + ($current_colspan-1)) * $column_width),
		$current_column_y + ($spacer/2),
		($column_width + $left_padding - $column_margin),
		17,
		"F");

$pdf->SetLeftMargin($current_left_margin + $left_padding + (($current_column + ($current_colspan-1)) * $column_width));
$pdf->SetRightMargin($page_width - ($current_left_margin + (($current_column + ($current_colspan-1)) * $column_width) + $column_width) + $right_padding);
$pdf->SetXY(($current_left_margin + (($current_column + ($current_colspan-1)) * $column_width) + $left_padding), $current_column_y + 3);
$pdf->WriteOptionlabel("<B>Thank you for voting!<BR>Please turn in<BR>your finished ballot</B>");


// Replace the "total pages" holder with the actual number
$pdf->replace = array("{t}");
$pdf->replacement = array("$current_page");




// MSIE hacks. Need this to be able to download the file over https
// All kudos to http://in2.php.net/manual/en/function.header.php#74736
header('Content-Transfer-Encoding: binary');
header('Cache-Control: maxage=3600'); //Adjust maxage appropriately
header('Pragma: public');



$pdf->Output("ballot-1-" . $ballot_hash . ".pdf", 'D');


?>